const std = @import("std");
const Allocator = std.mem.Allocator;
const rl = @import("raylib");
const math = std.math;
const utils = @import("utils.zig");
const setting = @import("setting.zig");

const IndexBlock = @import("map/index_block.zig");
const Food = @import("map/food.zig");
const Map = @import("map.zig");

const Move = @import("cell/move.zig");
const Force = @import("cell/force.zig");
const scan = @import("cell/scan.zig");
const Stomach = @import("cell/stomach.zig");
const SysEnergy = @import("cell/system_energy.zig");

pub const MaxShortWillpowerTime: u32 = 240; // 短期意志力持续时间上限

var cur_id: u32 = 0;

pub const Cell = @This();

alive: bool = true,
map: *Map,
index_block: *IndexBlock,

allocator: Allocator,
rand: *const std.Random,

id: u32,
x: f32,
y: f32,
radius: f32,
color: *const rl.Color,

short_willpower_time: u32, // 短期意志力持续时间下限

stomach: Stomach,
sys_energy: SysEnergy,
move: Move,
force: Force,
scan_range: f32,

pub fn init(x: ?f32, y: ?f32, radius: ?f32, color: ?*const rl.Color, map: *Map, rand: *const std.Random, allocator: Allocator) *@This() {
    const item = allocator.create(Cell) catch @panic("Out of memory");

    item.id = cur_id;
    if (null != x) {
        item.x = x.?;
    } else {
        item.x = map.randX();
    }
    if (null != y) {
        item.y = y.?;
    } else {
        item.y = map.randY();
    }
    if (null != radius) {
        item.radius = radius.?;
    } else {
        item.radius = setting.cell_radius;
    }
    if (null != color) {
        item.color = color.?;
    } else {
        item.color = setting.cell_color;
    }

    item.rand = rand;
    item.map = map;
    item.allocator = allocator;

    item.alive = true;
    item.stomach = Stomach.init(item);
    item.sys_energy = SysEnergy.init(item);
    item.move = Move.init(item);
    item.force = Force.init(item);
    item.scan_range = @floatFromInt(rand.intRangeAtMost(u8, setting.MinCellScanRange, setting.MaxCellScanRange));
    item.short_willpower_time = rand.intRangeLessThan(u32, 1, MaxShortWillpowerTime);

    map.index.index(@This(), item, true);

    cur_id += 1;

    return item;
}

pub fn deinit(self: *@This()) void {
    self.index_block.remove(@This(), self);

    self.stomach.deinit();
    self.sys_energy.deinit();
    self.move.deinit();
    self.force.deinit();

    self.allocator.destroy(self);
}

pub fn isInside(self: *const @This(), x: f32, y: f32) bool {
    const dx: f32 = self.x - x;
    const dy: f32 = self.y - y;

    return self.radius >= math.sqrt(dx * dx + dy * dy);
}

fn doFighting(cell: *Cell, enemy: *Cell) void {
    cell.force.fight(enemy);
}

pub fn fight(self: *@This(), enemy: *Cell) void {
    self.force.fight(enemy);
}

pub fn eat(self: *@This(), food: *Food) void {
    self.stomach.eat(food);
}

pub fn doAction(self: *@This()) void {
    self.move.walk4Nothing();
    self.map.index.index(@This(), self, false);
    scan.scanAndDo(self);
}

pub fn prepare(self: *@This()) void {
    self.stomach.drainEnergy(rl.getFrameTime());
}

pub fn do(self: *@This()) void {
    self.sys_energy.keepAlive(rl.getFrameTime());
    self.doAction();
}

pub fn post(self: *@This()) void {
    self.sys_energy.costFat(rl.getFrameTime());

    if (!self.alive) {
        self.index_block.destroy(@This(), self);
    }
}

const SegmentAngle: f32 = 15.0;
const CircleSize: f32 = 360.0;
pub fn draw(self: *@This()) void {
    rl.gl.rlBegin(rl.gl.rl_triangles);
    rl.gl.rlColor4ub(self.color.r, self.color.g, self.color.b, self.color.a);
    var angle: f32 = 0.0;
    var delta: f32 = 0.0;
    var startX: f32 = self.x + math.cos(0.0) * self.radius;
    var startY: f32 = self.y + math.sin(0.0) * self.radius;
    var endX: f32 = 0.0;
    var endY: f32 = 0.0;
    var notDone = true;
    while (notDone) {
        delta = angle + SegmentAngle;
        if (delta > CircleSize) {
            delta = CircleSize;
            notDone = false;
        }
        endX = self.x + math.cos(math.rad_per_deg * delta) * self.radius;
        endY = self.y + math.sin(math.rad_per_deg * delta) * self.radius;

        rl.gl.rlVertex2f(self.x, self.y);
        rl.gl.rlVertex2f(endX, endY);
        rl.gl.rlVertex2f(startX, startY);

        startX = endX;
        startY = endY;
        angle += SegmentAngle;
    }
    rl.gl.rlEnd();
}
